正如之前章节中提到过的,core.convert
是一个以通用为目的类型转换系统。它提供了同一个的ConversionService API和强类型的类型转换器SPI来实现一个类型到另一个类型的转换逻辑。一个Spring容器用这个系统来绑定Bean的属性值。另外,Spring Expression Language(SpEL)和DataBinder都使用这个系统去绑定字段值。比如,当SpEL需要强制将一个Short
变成Long
来完成expression.setValue(Object bean, Object value)
尝试时,core.convert
系统会执行这个强制转换。
现在,考虑下一些典型的客户端环境下的类型转换需求,比如web或桌面应用。在这样的环境中,你通常需要转成字符串支持客户端回发的过程,以及解析成字符串支持页面渲染的过程。另外,你也需要本地化字符串。一般的core.conveter
SPI达不到这种格式化要求。为了现实它们,Spring 3引入了一个便捷的格式化SPI,为客户端运行环境提供了简单健壮的选项代替PropertyEditors。
通常,在你需要实现通用的类型转换逻辑时使用Converter SPI;比如,java.util.Date
和java.lang.Long
之间的转换。而在你使用客户端环境,如一个web应用,且需要解析和打印本地化的字段值时使用Formatter SPI。ConversionService为两个SPI提供了统一的转换API类型。
Formatter SPI实现的字段给实话逻辑是简单且强类型的:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter继承了Printer和Parser接口:
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}
import java.text.ParseException;
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}
为了创建你自己的Formatter,只需要简单的实现上述接口。泛型参数T是你希望格式化的对象的类型,比如,java.util.Date
。实现print()
方法将T的实例以客户端本地的方式打印出来。实现parse()
方法,从客户端语言环境中返回一个格式化标准去解析T的实例。你的Formatter应该在尝试解析失败的时候抛出ParseException或IllegalArgumentException。确保你的Formatter的实现是线程安全的。
为了方便,在format
的子包中提供了许多Formatter的实现。number
包中提供了NumberFormatter
,CurrencyFormatter
和PercentFormatter
使用java.text.NumberFormat
去格式化java.lang.Number
。datetime
包提供了DateFormatter
使用java.text.DateFormatter
去格式化java.util.Date
。datetime.joda
包提供了基于Joda Time library复杂的日期格式化支持。
可以将DateFomatter
作为Formatter
实现的例子:
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
private String pattern;
public DateFormatter(String pattern) {
this.pattern = pattern;
}
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
dateFormat.setLenient(false);
return dateFormat;
}
}
Spring团队欢迎社区驱动的Formatter贡献;请看jira.spring.io来提供。
如你所见,字段格式化可以被字段类型或是注解配置。实现AnnotationFormatterFactory将注解绑定到Formatter。
package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
泛型参数A是你希望关联格式化逻辑的字段注解类型,比如org.springframework.format.annotation.DateTimeFormat
。getFieldTypes()
会返回可以被使用的字段类型。getPrinter()
会返回一个Printer打印被注解的字段的值。getParser()
会返回Parser解析一个被注解的客户端值。
下面的AnnotationFormatterFactory的实现将@NumberFormat注解绑定到了formatter上。这个注解允许指定数字风格或格式:
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
Short.class, Integer.class, Long.class, Float.class,
Double.class, BigDecimal.class, BigInteger.class }));
}
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
private Formatter<Number> configureFormatterFrom(NumberFormat annotation,
Class<?> fieldType) {
if (!annotation.pattern().isEmpty()) {
return new NumberFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyFormatter();
} else {
return new NumberFormatter();
}
}
}
}
用@NumberFormat简单注解字段就可以触发格式化:
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
org.springframework.format.annotation
包中有简便的格式化注解API。@NumberFormat用来格式化java.lang.Number
字段。@DateTimeFormat用来格式化java.util.Date
,java.util.Calendar
,java.util.Long
和Joda Time字段。
下面的例子用@DateTimeFormat将java.util.Date格式化成ISO日期(yyyy-MM-dd):
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
FormatterRegistry是一个用来注册formatters和converters的SPI。FormattingConversionService
是适用于大多数环境的FormatterRegistry实现。它可以在代码中被配置或是直接用FormattingConversionServiceFactoryBean
作为Spring bean使用。由于这个实现也实现了ConversionService
。它也可以直接配置被Spring的DataBinder
和Spring Expression Language(SpEL)使用。
查看下面的FormatterRegistry SPI:
package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Formatter<?> formatter);
void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);
}
正如上面所示,Formatter可以通过字段类型或是注解来注册。
FormatterRegistry SPI允许你集中的配置格式化规则,而不是在你的控制器里到处复制这样的配置。比如,你可能希望强制将所有的日期字段以某种方式格式化,或是将有注解的字段以某种方式格式化。通过共享FormatterRegistry
,你可以只定义一次这些规则,然后它们会在任何需要格式的地方被应用。
FormatterRegistrar是一个通过FormatterRegistry注册formatters和conveters的SPI。
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
当你需要为一个给定格式列别(比如日期格式)注册多个关联的conveters和formatter时,FormatterRegistrar是很有用的。在声明性注册不足的情况下也是有用的。例如,formatter需要在与自己的
在Spring MVC那章中查看21.16.3,"Conversion and Formatting"。